package com.example.jasonrpc.client;

import com.example.jasonrpc.codec.Decoder;
import com.example.jasonrpc.codec.Encoder;
import com.example.jasonrpc.proto.Request;
import com.example.jasonrpc.proto.Response;
import com.example.jasonrpc.proto.ServiceDescriptor;
import com.example.jasonrpc.transport.TransportClient;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 调用远程服务的代理类
 */
public class RemoteInvoker implements InvocationHandler {
    private static final Logger log = LoggerFactory.getLogger(RemoteInvoker.class);
    private Class clazz;
    private Encoder encoder;
    private Decoder decoder;
    private TransportSelector selector;

    public RemoteInvoker(Class clazz, Encoder encoder, Decoder decoder, TransportSelector selector) {
        this.clazz = clazz;
        this.encoder = encoder;
        this.decoder = decoder;
        this.selector = selector;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        Request request = new Request();
        request.setService(ServiceDescriptor.build(clazz, method));
        request.setParameters(args);

        Response response = invokeRemote(request);
        if (response == null || response.getCode() != 0) {
            throw new IllegalStateException("Fail to invoke remote: " + response);
        }
        return response.getData();
    }

    private Response invokeRemote(Request request) {
        TransportClient client = null;
        String serviceName = request.getService().getClazz();
        Response response = new Response();
        try {
            client = selector.select(serviceName);
            byte[] outBytes = encoder.encode(request);
            InputStream in = client.write(new ByteArrayInputStream(outBytes));
            byte[] inBytes = IOUtils.readFully(in, in.available());
            response = decoder.decode(inBytes, Response.class);
        } catch (IOException e) {
            log.warn(e.getMessage(), e);
            response.setCode(1);
            response.setMessage("RpcClient got errlr: " + e.getClass() + ", " + e.getMessage());
        } finally {
            if (client != null) {
                selector.release(client, serviceName);
            }
        }
        return response;
    }

    public Encoder getEncoder() {
        return encoder;
    }

    public void setEncoder(Encoder encoder) {
        this.encoder = encoder;
    }

    public Decoder getDecoder() {
        return decoder;
    }

    public void setDecoder(Decoder decoder) {
        this.decoder = decoder;
    }

    public TransportSelector getSelector() {
        return selector;
    }

    public void setSelector(TransportSelector selector) {
        this.selector = selector;
    }
}
